"""
题目：https://leetcode-cn.com/problems/house-robber-iii/
    在上次打劫完一条街道之后和一圈房屋后，小偷又发现了一个新的可行窃的地区。这个地区只有一个入口，我们称之为“根”。 除了“根”之外，每栋房子有且只有一个“父“房子与之相连。一番侦察之后，聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫，房屋将自动报警。
    计算在不触动警报的情况下，小偷一晚能够盗取的最高金额。
    示例 1:
    输入: [3,2,3,null,3,null,1]
        3
        / \
    2   3
        \   \ 
        3   1
    输出: 7 
    解释: 小偷一晚能够盗取的最高金额 = 3 + 3 + 1 = 7.

思路：动态规划.
    用 f(o) 表示选择 o 节点的情况下，o 节点的子树上被选择的节点的最大权值和；
    g(o) 表示不选择 o 节点的情况下，o 节点的子树上被选择的节点的最大权值和；l 和 r 代表 o 的左右孩子。
    当 o 被选中时，o 的左右孩子都不能被选中，故 o 被选中情况下子树上被选中点的最大权值和为 l 和 r 不被选中的最大权值和相加，即 f(o) = g(l) + g(r).
    当 o 不被选中时，o 的左右孩子可以被选中，也可以不被选中。对于 o 的某个具体的孩子 x，它对 o 的贡献是 x 被选中和不被选中情况下权值和的较大值。故 g(o) = \max \{ f(l) , g(l)\}+\max\{ f(r) , g(r) \}。
    至此，我们可以用哈希表来存 f 和 g 的函数值，用深度优先搜索的办法后序遍历这棵二叉树，我们就可以得到每一个节点的 f 和 g。根节点的 f 和 g 的最大值就是我们要找的答案。

时间复杂度：O(n)
空间复杂度：O(n)
"""

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def __init__(self):
        self.f = {None:0}
        self.g = {None:0}

    def rob(self, root: TreeNode) -> int:
        self.dfs(root)
        return max(self.f[root],self.g[root])

    def dfs(self,node):
        if not node:
            return
        self.dfs(node.left)
        self.dfs(node.right)
        self.f[node] = node.val + self.g[node.left] + self.g[node.right]
        self.g[node] = max(self.f[node.left],self.g[node.left]) + max(self.f[node.right],self.g[node.right])